/*
 * Copyright 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0

 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.util.compat;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.JavacTask;
import java.io.BufferedReader;
import java.io.BufferedWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.lang.model.element.Modifier;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

public class CoreExtractorAST {
  private static int counter = 0;
  private static String tempdir = "japicv";
  
  public static List<ClassNode> extract(File sourceRoot) throws IOException {
    return extract(sourceRoot, new ArrayList<ClassNode>(),
        new ArrayList<String>());
  }
  
  public static List<ClassNode> extract(File sourceRoot, List<ClassNode>
      exclusions, List<String> exclusionPkgs) throws IOException {
    List<ClassNode> retValue = new ArrayList<ClassNode>();
    
    if (sourceRoot.getCanonicalPath().endsWith(".jar"))
      sourceRoot = constructTemp(sourceRoot);
    
    List<File> files = new ArrayList<File>();
    constructFileTree(sourceRoot, files);
    
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    JavacTask task = (JavacTask)compiler.getTask(null, null, null, null, null,
        compiler.getStandardFileManager(null, null, null)
        .getJavaFileObjectsFromFiles(files));
    
    for (CompilationUnitTree c : task.parse()) {
      boolean pkgConsider = true;
      String currentPkg = c.getPackageName().toString();
      for (String pkgname : exclusionPkgs)
        if (currentPkg.startsWith(pkgname)) {
          pkgConsider = false;
          break;
        }
      if (pkgConsider)
        for (Tree rt : c.getTypeDecls())
          retValue.addAll(parseClass((ClassTree)rt, currentPkg, "", exclusions));
    }
      
    Collections.sort(retValue);
    return retValue;
  }
  
  private static List<ClassNode> parseClass(ClassTree root, String pkgname,
      String parent, List<ClassNode> exclusions) {
    List<ClassNode> retValue = new ArrayList<ClassNode>();
    
    Set<Modifier> flags = root.getModifiers().getFlags();
    if (!(flags.contains(Modifier.PUBLIC) || flags.contains(Modifier.PROTECTED)))
      return retValue;
    ClassNode current = new ClassNode(pkgname,
        parent + root.getSimpleName().toString(), flags);
    for (Tree iface : root.getImplementsClause())
      current.addInterface(iface.toString());
    if (root.getExtendsClause() != null)
      current.setParent(root.getExtendsClause().toString());
    for (Tree member : root.getMembers())
      if (member.getKind() == Tree.Kind.VARIABLE) {
        VariableTree vtree = (VariableTree)member;
        Set<Modifier> mods = vtree.getModifiers().getFlags();
        FieldNode fn = getField(vtree);
        int index = exclusions.indexOf(current);
        if (index >= 0 && exclusions.get(index).fields.contains(fn))
          continue;
        if (mods.contains(Modifier.PUBLIC) || mods.contains(Modifier.PROTECTED))
          current.addField(fn);
      }
      else if (member.getKind() == Tree.Kind.METHOD) {
        MethodTree mtree = (MethodTree)member;
        Set<Modifier> mods = mtree.getModifiers().getFlags();
        if (mods.contains(Modifier.PUBLIC) || mods.contains(Modifier.PROTECTED)) {
          List<FieldNode> params = new ArrayList<FieldNode>();
          for (VariableTree vt : mtree.getParameters())
            params.add(getField(vt));
          String mname = mtree.getName().toString();
          if (mname.equals("<init>"))
            mname = root.getSimpleName().toString();
          MethodNode mn = new MethodNode(mname, mtree.getReturnType(), mods,
              params);
          MethodNode mExcluded = null;
          try {
            List<MethodNode> temp = exclusions.get(exclusions.indexOf(current)).methods;
            mExcluded = temp.get(temp.indexOf(mn));
          } catch(IndexOutOfBoundsException e) {}
          if (mExcluded != null && mExcluded.exceptions.isEmpty())
            continue;
          for (ExpressionTree et : mtree.getThrows())
            if (!(mExcluded != null && mExcluded.exceptions.contains(et.toString())))
              mn.addException(et.toString());
          current.addMethod(mn);
        }
      }
      else if (member.getKind() == Tree.Kind.CLASS)
        retValue.addAll(parseClass((ClassTree)member, pkgname,
            current.name + ".", exclusions));
    if (exclusions.contains(current)) {
      ClassNode temp = exclusions.get(exclusions.indexOf(current));
      if (temp.fields.isEmpty() && temp.methods.isEmpty())
        return retValue;
    }
    current.done();
    retValue.add(current);
    
    return retValue;
  }
  
  private static FieldNode getField(VariableTree vt) {
    return new FieldNode(vt.getName().toString(), vt.getType().toString(),
        vt.getModifiers().getFlags());
  }
  
  private static void constructFileTree(File current, List<File> list) {
    if (current.isFile() && current.getName().endsWith(".java"))
      list.add(current);
    else if (current.isDirectory())
      for (File f : current.listFiles())
        constructFileTree(f, list);
  }

  private static File constructTemp(File sourceRoot) throws IOException {
    String tempRootPath = System.getProperty("java.io.tmpdir") + File.separator +
        tempdir + (++counter) + File.separator;
    File tempRoot = new File(tempRootPath);
    tempRoot.mkdir();
    tempRoot.deleteOnExit();
    JarFile jar = new JarFile(sourceRoot);
    Enumeration<JarEntry> en = jar.entries();
    JarEntry je;
    while (en.hasMoreElements()) {
      je = en.nextElement();
      File current = new File(tempRootPath + je.getName());
      current.deleteOnExit();
      if (je.isDirectory())
        current.mkdirs();
      if (je.getName().endsWith(".java")) {
        current.createNewFile();
        BufferedReader in = new BufferedReader(new InputStreamReader(jar.getInputStream(je)));
        BufferedWriter out = new BufferedWriter(new FileWriter(current));
        while (in.ready()) {
          out.write(in.readLine());
          out.newLine();
        }
        out.close();
        in.close();
      }
    }
    return tempRoot;
  }
  
}